
#include <material/materialmanager.h>
#include <material/programeditor/programeditordialog.h>
#include <material/shadereditor/shadertreemodel.h>
#include <config.h>
#include <session.h>
#include <assert.h>
#include <QGraphicsSceneDragDropEvent>
#include <QMimeData>
#include <qevent.h>
#include <qmessagebox.h>

cceMaterialProgramPrimaryItem::cceMaterialProgramPrimaryItem ()
  : cceMaterialPrimaryItem (0)
{
	SetType ("<<Program>>");
}

void cceMaterialProgramPrimaryItem::AddTechnique ()
{
	AddTechniqueItem ();

	// update the view
	GetView ()->UpdateLayout ();
	emit Changed();
}

cceMaterialProgramTechniqueItem* cceMaterialProgramPrimaryItem::AddTechniqueItem ()
{
	cceMaterialProgramTechniqueItem* item = new cceMaterialProgramTechniqueItem (this);
	connect (item, SIGNAL(Changed()), this, SIGNAL(Changed()));
  connect (item, SIGNAL(ShowEditor(cceMaterialItem*)), this, SIGNAL(ShowEditor(cceMaterialItem*)));
	AddChild (item);
	return item;
}

void cceMaterialProgramPrimaryItem::RemoveTechnique (cceMaterialProgramTechniqueItem* technique)
{
	disconnect (technique, SIGNAL(Changed()), this, SIGNAL(Changed()));
  disconnect (technique, SIGNAL(ShowEditor(cceMaterialItem*)), this, SIGNAL(ShowEditor(cceMaterialItem*)));
  RemoveChild (technique);

	// cleanup
	technique->deleteLater ();
	// update the view
	GetView ()->UpdateLayout ();
	emit Changed();
}

void cceMaterialProgramPrimaryItem::MoveTechnique (cceMaterialProgramTechniqueItem* technique, int direction)
{
	QList<cceMaterialBaseItem*>& children = GetChildrenList ();

	int idxOld = children.indexOf (technique);
	int idxNew = idxOld + direction;
	if (idxNew < 0 || idxNew >= children.size ())
		{
			return;
		}

	children.swap (idxOld, idxNew);

	// update the view
	GetView ()->UpdateLayout ();
	emit Changed();
}

unsigned cceMaterialProgramPrimaryItem::GetNumberOfTechniques () const
{
	return GetChildren ().size ();
}

cceMaterialProgramTechniqueItem* cceMaterialProgramPrimaryItem::GetTechnique (unsigned idx)
{
	return static_cast<cceMaterialProgramTechniqueItem*>(GetChildrenList ()[idx]);
}

cceMaterialProgramTechniqueItem::cceMaterialProgramTechniqueItem (cceMaterialProgramPrimaryItem* primary)
	: cceMaterialTechniqueItem ()
	, _primary (primary)
{
	SetName ("<<Technique>>");

	_slot = new cceMaterialShaderSlotItem (this);
	AddChild (_slot);
}

unsigned cceMaterialProgramTechniqueItem::GetNumberOfShaders ()
{
	return _shaders.size ();
}

cceMaterialShaderItem* cceMaterialProgramTechniqueItem::GetShader (unsigned idx)
{
	return _shaders[idx];
}

void cceMaterialProgramTechniqueItem::RemoveTechnique ()
{
	RemoveChild (_slot);
	_slot->deleteLater ();

	foreach (cceMaterialShaderItem* item, _shaders)
		{
			RemoveChild (item);
			item->deleteLater ();
		}
	// remove the shaders here.
	_primary->RemoveTechnique (this);
}

void cceMaterialProgramTechniqueItem::MoveUp ()
{
	_primary->MoveTechnique (this, -1);
}

void cceMaterialProgramTechniqueItem::MoveDown ()
{
	_primary->MoveTechnique (this, 1);
}

cceMaterialShaderItem* cceMaterialProgramTechniqueItem::AddShader (cceMaterialShader* shader)
{
	foreach (cceMaterialShaderItem* item, _shaders)
		{
			if (item->IsShader (shader))
				{
					return 0;
				}
		}

	RemoveChild (_slot);

	cceMaterialShaderItem* shaderItem = new cceMaterialShaderItem (this, shader);
	connect (shaderItem, SIGNAL(Changed()), this, SIGNAL(Changed()));
  connect (shaderItem, SIGNAL(ShowEditor(cceMaterialItem*)), this, SIGNAL(ShowEditor(cceMaterialItem*)));
	_shaders.push_back (shaderItem);
	AddChild (shaderItem);

	AddChild (_slot);

	emit Changed ();
	return shaderItem;

}

void cceMaterialProgramTechniqueItem::RemoveShader (cceMaterialShaderItem* shader)
{
	_shaders.removeAll (shader);

	disconnect (shader, SIGNAL(Changed()), this, SIGNAL(Changed()));
  disconnect (shader, SIGNAL(ShowEditor(cceMaterialItem*)), this, SIGNAL(ShowEditor(cceMaterialItem*)));

	RemoveChild (shader);
  shader->deleteLater();

	// update the view
	GetView ()->UpdateLayout ();
	emit Changed ();
}



cceMaterialShaderSlotItem::cceMaterialShaderSlotItem (cceMaterialProgramTechniqueItem* technique)
	: cceMaterialSlotItem ()
	, _technique (technique)
{
	SetName ("<<Add shader>>");
}

namespace
{
cceMaterialShader* read_shader_from_mime (const QMimeData* mimeData)
{
	if (!mimeData)
		{
			return 0;
		}

	if (!mimeData->hasFormat (CCE_MATERIAL_SHADERNAME_MIME))
		{
			return 0;
		}

	QByteArray ba = mimeData->data (CCE_MATERIAL_SHADERNAME_MIME);
	return cceSession::Get ()->GetMaterialManager ()->GetShader (QString (ba));
}
}
void cceMaterialShaderSlotItem::dragEnterEvent (QGraphicsSceneDragDropEvent* evt)
{
	cceMaterialShader* shader = read_shader_from_mime (evt->mimeData ());
	if (shader)
		{
			evt->accept ();
		}
	else
		{
			evt->ignore ();
		}
}

void cceMaterialShaderSlotItem::dropEvent (QGraphicsSceneDragDropEvent* evt)
{
	cceMaterialShader* shader = read_shader_from_mime (evt->mimeData());
	if (!shader)
		{
			return;
		}

	_technique->AddShader (shader);

	// update the view
	GetView ()->UpdateLayout ();
}


cceMaterialShaderItem::cceMaterialShaderItem (cceMaterialProgramTechniqueItem* technique, cceMaterialShader* shader)
	: cceMaterialValueItem ()
	, _technique (technique)
	, _shader (shader)
{
  QString typeName = QString ("<<%1>>").arg(cceMaterialShader::GetTypeName(shader->GetType()));
  SetType(typeName);
	setAcceptDrops(true);
	SetName (shader->GetName ());
  connect (shader, SIGNAL(Changed()), this, SLOT(onShaderChanged ()));
  connect (shader, SIGNAL(Removed()), this, SLOT(RemoveValue()));

  connect (this, SIGNAL(Clicked()), this, SLOT(ShaderClicked()));
}

void cceMaterialShaderItem::onShaderChanged()
{
  QString typeName = QString ("<<%1>>").arg(cceMaterialShader::GetTypeName(_shader->GetType()));
  SetType(typeName);
  SetName (_shader->GetName ());

  GetView()->UpdateLayout();
	emit Changed ();
}

void cceMaterialShaderItem::ShaderClicked()
{
  emit ShowEditor(_shader);
}


cceMaterialShader* cceMaterialShaderItem::GetShader () 
{
	return _shader;
}

bool cceMaterialShaderItem::IsShader (cceMaterialShader* shader)
{
	return _shader == shader;
}

void cceMaterialShaderItem::RemoveValue ()
{
	_technique->RemoveShader (this);
}


void cceMaterialShaderItem::dragEnterEvent (QGraphicsSceneDragDropEvent* evt)
{
	cceMaterialShader* shader = read_shader_from_mime (evt->mimeData ());
	if (shader)
		{
			evt->accept ();
		}
	else
		{
			evt->ignore ();
		}
}

void cceMaterialShaderItem::dropEvent (QGraphicsSceneDragDropEvent* evt)
{
	cceMaterialShader* shader = read_shader_from_mime (evt->mimeData());
	if (!shader)
		{
			return;
		}

	_shader = shader;
	onShaderChanged();
}

cceMaterialProgramEditorDialog::cceMaterialProgramEditorDialog(QWidget* parent)
  : cceMaterialEditorAbstract (parent)
	, _program (0)
	, _primaryItem (0)
{
	_gui.setupUi(this);


	_programEditorView = new cceMaterialView (_gui.frameContent);
	QGridLayout* contentLayout = new QGridLayout (_gui.frameContent);
	contentLayout->setContentsMargins (0, 0, 0, 0);
	contentLayout->addWidget (_programEditorView, 0, 0, 1, 1);
}

void cceMaterialProgramEditorDialog::SetProgram (cceMaterialProgram* program)
{
	_program = program;
  connect (_program, SIGNAL(Removed()), this, SLOT(ForceClose ()));

	UpdateGUIFromProgram ();
}

void cceMaterialProgramEditorDialog::ForceClose()
{
  emit CloseEditor(_program);
}


void cceMaterialProgramEditorDialog::on_lineEditName_textChanged (const QString& text)
{
	if (_primaryItem)
		{
			_primaryItem->SetName (text + ".prog");
			_programEditorView->UpdateLayout ();
		}
  SetChanged(ProgramChanged());
}


void cceMaterialProgramEditorDialog::Changed ()
{
  SetChanged(ProgramChanged());
}

void cceMaterialProgramEditorDialog::UpdateGUIFromProgram ()
{
	assert (_program);
	_gui.lineEditName->setText (GetEditorName(_program->GetName ()));

	_primaryItem = new cceMaterialProgramPrimaryItem ();
	_primaryItem->SetName (_program->GetName ());
	connect (_primaryItem, SIGNAL(Changed()), this, SLOT(Changed ()));
  connect (_primaryItem, SIGNAL(ShowEditor(cceMaterialItem*)), this, SIGNAL(ShowEditor(cceMaterialItem*)));

	for (unsigned i=0, j=_program->GetNumberOfTechniques (); i<j; ++i)
		{
			cceMaterialProgramTechnique* technique = _program->GetTechnique (i);
			cceMaterialProgramTechniqueItem* techniqueItem = _primaryItem->AddTechniqueItem ();

			for (unsigned k=0, l=technique->GetNumberOfShaders (); k<l; ++k)
				{
					techniqueItem->AddShader (technique->GetShader (k));
				}
		}


	_programEditorView->SetItem (_primaryItem);
}

bool cceMaterialProgramEditorDialog::UpdateProgramFromGUI ()
{
	assert (_program);

	cceMaterialProgram *prog = _manager->GetProgram(_gui.lineEditName->text() + ".prog");
	if (prog && prog != _program)
		{
			QMessageBox::information(this,
															 "Program - " CCE_TOOL_NAME,
															 "The program name is already in use.\nPlease enter a unique program name.");
			return false;
		}

	_program->SetName (_gui.lineEditName->text () + ".prog");

	_program->Clear ();

	for (unsigned i=0, j=_primaryItem->GetNumberOfTechniques (); i<j; ++i)
		{
			cceMaterialProgramTechnique* technique = new cceMaterialProgramTechnique ();
			cceMaterialProgramTechniqueItem* techniqueItem = _primaryItem->GetTechnique (i);
			_program->AddTechnique (technique);

			for (unsigned k=0, l=techniqueItem->GetNumberOfShaders (); k<l; ++k)
				{
					cceMaterialShaderItem* shaderItem = techniqueItem->GetShader (k);
					cceMaterialShader* shader = shaderItem->GetShader ();

					technique->AddShader (shader);
				}

		}

  if (_program->IsNew() && _manager)
    {
      _manager->AddProgram(_program);
    }
	SetChanged(false);
  return true;
}


QString cceMaterialProgramEditorDialog::GetName () const
{
	if (_program)
		{
			return _program->GetName();
		}
	return "";
}

bool cceMaterialProgramEditorDialog::Save()
{
  return UpdateProgramFromGUI();
}

void cceMaterialProgramEditorDialog::Close ()
{
	if (ProgramChanged ())
		{
			int result = QMessageBox::question (this,
				"Program changed - " CCE_TOOL_NAME,
				"The program has unsaved changes. Save changes bevor closing?",
				QMessageBox::Save, QMessageBox::Close, QMessageBox::Cancel);
			switch (result)
				{
				case QMessageBox::Save:
					Save ();
					break;

				case QMessageBox::Cancel:
					return;

				case QMessageBox::Close:
					break;
				}

		}
  emit CloseEditor(_program);
}

bool cceMaterialProgramEditorDialog::ProgramChanged ()
{
  if (!_program || !_primaryItem)
    {
      return false;
    }
	if (_gui.lineEditName->text () != GetEditorName(_program->GetName ()))
		{
			return true;
		}
	if (_program->GetNumberOfTechniques () != _primaryItem->GetNumberOfTechniques ())
		{
			return true;
		}

	for (unsigned i=0, j=_program->GetNumberOfTechniques (); i<j; ++i)
		{
			cceMaterialProgramTechnique* technique = _program->GetTechnique (i);
			cceMaterialProgramTechniqueItem* techniqueItem = _primaryItem->GetTechnique (i);

			if (technique->GetNumberOfShaders () != techniqueItem->GetNumberOfShaders ())
				{
					return true;
				}

			unsigned numShaders = technique->GetNumberOfShaders();
			for (unsigned k=0; k<numShaders; ++k)
				{
					cceMaterialShader* shader = technique->GetShader (k);
					bool found = false;
					for (unsigned l=0; l<numShaders; ++l)
						{
							cceMaterialShaderItem* shaderItem = techniqueItem->GetShader (l);
							if (shaderItem->IsShader (shader))
								{
									found = true;
									break;
								}
						}
					if (!found)
						{
							return true;
						}
				}
		}

	return false;
}

